/**************************************************************************************************
 * red library                                                                                    *
 * Copyright © 2012 David Kretzmer                                                                *
 *                                                                                                *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software  *
 * and associated documentation files (the "Software"), to deal in the Software without           *
 * restriction,including without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the   *
 * Software is furnished to do so, subject to the following conditions:                           *
 *                                                                                                *
 * The above copyright notice and this permission notice shall be included in all copies or       *
 * substantial portions of the Software.                                                          *
 *                                                                                                *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING  *
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND     *
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.        *
 *                                                                                                *
 *************************************************************************************************/

#pragma once

#include "light/light.hpp"

#include <cmath>


namespace red
{
    using namespace light::types;


	//=============================================================================================
	// Import useful functions
	//=============================================================================================
	using ::std::sqrt;


	//=============================================================================================
	// Checks whether the given type is a vector. Must be specialized for each vector type.
	//=============================================================================================
	template<typename VectorType>
	struct is_vector
	{
		static bool const value = false;
	};


	//=============================================================================================
	// Gets the dimension of the given vector. If 'VEC' is a static vector, the result
	// is a constant expression (and this is the reason for DIMENSION being such a nasty macro)
	//=============================================================================================
	namespace internal
	{
		// Depending on the type of the vector (dynamic or static) we have to enable/disable access
		// to the dimension because for static vectors it is a static member whereas for dynamic it
		// is a member function.It would lead to compiler errors if both methods of accessing where
		// enabled.

		template<bool Enable, typename Vector>
		struct static_dimension
		{
			static size_t const value = 0;
		};
		template<typename Vector>
		struct static_dimension<true, Vector>
		{
			static size_t const value = Vector::dimension;
		};

		template<bool Enable>
		struct dynamic_dimension
		{
			template<typename Vector>
			static size_t value(Vector const &vec)
			{
				return vec.dimension();
			}
		};
		template<>
		struct dynamic_dimension<false>
		{
			template<typename Vector>
			static size_t value(Vector const &)
			{
				return 0;
			}
		};
	}

	#define RED_DIMENSION(VEC) (::light::remove_qualifiers<decltype(VEC)>::type::is_static ?                                                    \
	                                ::red::internal::static_dimension< ::light::remove_qualifiers<decltype(VEC)>::type::is_static,              \
	                                                            typename ::light::remove_qualifiers<decltype(VEC)>::type>::value                \
	                            :                                                                                                               \
	                                ::red::internal::dynamic_dimension<!::light::remove_qualifiers<decltype(VEC)>::type::is_static>::value(VEC))


	//=============================================================================================
	// Gets the number of rows and columns of the given matrix. If 'MAT' is a static vector, the
	// result is a constant expression (and this is the reason for ROWS/COLUMNS being such a nasty
	// macro)
	//=============================================================================================
	namespace internal
	{
		// Depending on the type of the matrix (dynamic or static) we have to enable/disable access
		// to the number of rows/columns because for static vectors it is a static member whereas for
		// dynamic ones it is a member function. It would lead to compiler errors if both methods of
		// accessing where enabled.

		template<bool Enable, typename Matrix>
		struct static_rows { enum { value = 0 }; };
		template<typename Matrix>
		struct static_rows<true, Matrix> { static size_t const value = Matrix::rows; };

		template<bool Enable, typename Matrix>
		struct static_columns { enum { value = 0 }; };
		template<typename Matrix>
		struct static_columns<true, Matrix> { static size_t const value = Matrix::columns; };

		template<bool Enable>
		struct dynamic_rows
		{
			template<typename Matrix>
			static size_t value(Matrix const &)
			{
				return 0;
			}
		};
		template<>
		struct dynamic_rows<true>
		{
			template<typename Matrix>
			static size_t value(Matrix const &mat)
			{
				return mat.rows();
			}
		};

		template<bool Enable>
		struct dynamic_columns
		{
			template<typename Matrix>
			static size_t value(Matrix const &)
			{
				return 0;
			}
		};
		template<>
		struct dynamic_columns<true>
		{
			template<typename Matrix>
			static size_t value(Matrix const &mat)
			{
				return mat.columns();
			}
		};
	}

	#define RED_ROWS(MAT) (::light::remove_qualifiers<decltype(MAT)>::type::is_static ?                                                     \
	                                ::red::internal::static_rows< ::light::remove_qualifiers<decltype(MAT)>::type::is_static,               \
	                                                            typename ::light::remove_qualifiers<decltype(MAT)>::type>::value            \
	                            :                                                                                                           \
	                                ::red::internal::dynamic_rows<!::light::remove_qualifiers<decltype(MAT)>::type::is_static>::value(MAT))

	#define RED_COLUMNS(MAT) (::light::remove_qualifiers<decltype(MAT)>::type::is_static ?                                                     \
									::red::internal::static_columns< ::light::remove_qualifiers<decltype(MAT)>::type::is_static,               \
																typename ::light::remove_qualifiers<decltype(MAT)>::type>::value               \
								:                                                                                                              \
									::red::internal::dynamic_columns<!::light::remove_qualifiers<decltype(MAT)>::type::is_static>::value(MAT))


	//=============================================================================================
	// Checks whether the given type is a matrix. Must be specialized for each matrix type.
	//=============================================================================================
	template<typename MatrixType>
	struct is_matrix
	{
		static bool const value = false;
	};


	//=============================================================================================
	// For two given operands, returns the type of the result.
	// Usually, 'OperandA' and 'OperandB' must be of the same type, and 'type' is set to that type.
	// An exception are the 'extern_*' types.
	//=============================================================================================
	template<typename OperandA, typename OperandB>
	struct result_type;

	template<typename Operand>
	struct result_type<Operand, Operand>
	{
		typedef Operand type;
	};

	template<typename Operand>
	struct unary_result_type
	{
		typedef Operand type;
	};

} // namespace: red
